#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#include "typedefs.h"
#include "chip.h"
#include "hbi.h"

#define ROUT_GAIN_DB_MAX 51
#define ROUT_GAIN_DB_MIN -24
#define ROUT_GAIN_DB_STEP 0.375
#define ROUT_extGAIN_DB_STEP 6
#define ROUT_GAIN_0DB_REF 64
#define ROUT_uGAIN_DB_MAX 21
#define ROUT_extGAIN_DB_MAX 30


#define SOUT_GAIN_DB_MAX 21
#define SOUT_GAIN_DB_MIN -24
#define SOUT_GAIN_DB_STEP 3
#define SOUT_GAIN_0DB_REF 8

/*
*
********************************************************************************/


/*! \ingroup zl380xx_audio_mixer
 *  read or write ZL380xx device rout or sout gain"
 *  The sout gain range is from -24 to +21 at +/-3dB step
 *  The Rout gain range is from -24 to +21 at +/-0.375dB step
 *  Additionally, the rout gain has extra range from +27 - 51dB at +/-6dB step
 *  to read current ROUT gain:  ./zl380xx_audio_mixer -get rout\n");
 *  to apply 10dB gain to ROUT: ./zl380xx_audio_mixer -set rout 10\n");
 *  to read current SOUT gain:  ./zl380xx_audio_mixer -get sout\n");
 *  to apply 10dB gain to SOUT: ./zl380xx_audio_mixer -set sout 10\n");
 *
*/
/*#define I2C*/
#ifdef I2C
static int bus_num = 0;
static int dev_id = 0x45;
#else
static int bus_num = 0;
static int dev_id = 1;
#endif

/* size of buffer for HBI read/write*/
#define MAX_RW_SIZE 64

/*round float to the nearest integer*/
int roundftoint(float num)
{
    int n = (int)(num < 0 ? (num - 0.5) : (num + 0.5));
    printf("%d\n", n);
    return n;
}

/*
 * the range from -24 to +21 is at +/-3dB step
 */
static hbi_status_t zl380xx_set_sout_gain(hbi_handle_t handle, int gaindB)
{
    hbi_status_t status = HBI_STATUS_SUCCESS;
    user_buffer_t buf[2];
    
    if ((gaindB > SOUT_GAIN_DB_MAX) ||  (gaindB < SOUT_GAIN_DB_MIN))
    {
        printf("Gain %d dB is out of range(-%d to + %d dB)!!!\n", gaindB, SOUT_GAIN_DB_MIN, SOUT_GAIN_DB_MAX);
        return HBI_STATUS_INVALID_ARG; 
    }

    status = HBI_read(handle, ZL380xx_SOUT_GAIN_CTRL_REG, buf, 2);
    if(status != HBI_STATUS_SUCCESS)
    { 
       return status;       
    }
    
    buf[0] = SOUT_GAIN_0DB_REF + (gaindB/SOUT_GAIN_DB_STEP);
    
    return HBI_write(handle,ZL380xx_SOUT_GAIN_CTRL_REG,buf,2);
}

/* gain range -24dB to +21dB */
static hbi_status_t zl380xx_get_sout_gain(hbi_handle_t handle, int *pGaindB)
{
    hbi_status_t status = HBI_STATUS_SUCCESS;
    user_buffer_t buf[2];
    
    status = HBI_read(handle, ZL380xx_SOUT_GAIN_CTRL_REG, buf, 2);
    if(status != HBI_STATUS_SUCCESS)
    { 
       return status;       
    }
    printf("soutgain = 0x%02x, singain = 0x%02x\n", buf[0], buf[1]);
    /*calculate the Sout gain from the first 7-bits + the last 9 bits in dB*/
    *pGaindB = roundftoint(SOUT_GAIN_DB_STEP * (buf[0] - SOUT_GAIN_0DB_REF)); 
     
    return status;
}
     

/*total gain range -24dB to +51dB 
 *the range from -24 to +21 is at +/-0.375 dB step
 *the ext range above +21 up to +30 is at +/-6dB steps
 */
static hbi_status_t zl380xx_set_rout_gain(hbi_handle_t handle, int gaindB)
{
    user_buffer_t buf[2];
    uint16_t gain;
    
    if ((gaindB > ROUT_GAIN_DB_MAX) ||  (gaindB < ROUT_GAIN_DB_MIN))
    {
        printf("Gain %d dB is out of range(-%d to + %d dB)!!!\n", gaindB, ROUT_GAIN_DB_MIN, ROUT_GAIN_DB_MAX);
        return HBI_STATUS_INVALID_ARG; 
    }
    if (gaindB > ROUT_uGAIN_DB_MAX) 
    {
        buf[0] = ((gaindB - ROUT_uGAIN_DB_MAX)/ROUT_extGAIN_DB_STEP);        
        buf[1] = (ROUT_GAIN_0DB_REF + ROUT_uGAIN_DB_MAX/ROUT_GAIN_DB_STEP);
        gain = (buf[0]<<7) | buf[1];        
        buf[0] = (gain >> 8);
        buf[1] = (gain & 0xFF);      
    } else
    {
        buf[0] = 0;
        buf[1] = ROUT_GAIN_0DB_REF + gaindB/ROUT_GAIN_DB_STEP; 
    }
    return HBI_write(handle,ZL380xx_ROUT_GAIN_CTRL_REG,buf,2);
}

/* gain range -24dB to +51dB */
static hbi_status_t zl380xx_get_rout_gain(hbi_handle_t handle, int *pGaindB)
{
    hbi_status_t status = HBI_STATUS_SUCCESS;
    user_buffer_t buf[2];
    uint16_t gain;
    uint8_t ugain, extgain = 0;
    
    status = HBI_read(handle, ZL380xx_ROUT_GAIN_CTRL_REG, buf, 2);
    if(status != HBI_STATUS_SUCCESS)
    { 
       return status;       
    }
    gain = (buf[0]<<8) | (buf[1]);
    ugain = (gain & 0x7F);
    if (gain > (ROUT_GAIN_0DB_REF+ ROUT_uGAIN_DB_MAX/ROUT_GAIN_DB_STEP)) 
        extgain = (gain >> 7);
    else 
        extgain = 0;
        
    /*calculate the rout gain from the first 7-bits + the last 9 bits*/
    *pGaindB = roundftoint((ROUT_GAIN_DB_STEP * (ugain - ROUT_GAIN_0DB_REF)) +
             (extgain * ROUT_extGAIN_DB_STEP)); 
    printf("gain = 0x%04x, ugain = 0x%02x, extgain = 0x%02x\n", gain, ugain, extgain);
     
    return status;
}

static void usage(void)
{
   printf("Usage: \n");

   printf("read or write ZL380xx device rout or sout gain");
   printf("to read current ROUT gain:  ./test_mixer -get rout\n");
   printf("to apply 10dB gain to ROUT: ./test_mixer -set rout 10\n");
   printf("to read current SOUT gain:  ./test_mixer -get sout\n");
   printf("to apply 10dB gain to SOUT: ./test_mixer -set sout 10\n");
   printf("<options>: is either rout or sout\n");

   return;
}

int main(int argc, char** argv) {
   hbi_handle_t handle;
   hbi_status_t status = HBI_STATUS_SUCCESS;
   hbi_dev_cfg_t devcfg;
   int    gain;
   int           i=1;
   
   if((strcmp(argv[i],"-h")==0) || (argc < 3))
   {
       usage();
       return 1;
   }

   status = HBI_init(NULL);
   if (status != HBI_STATUS_SUCCESS)
   {
       printf("HBI_init failed\n");
       return -1;
   }

   devcfg.dev_addr = dev_id;
   devcfg.bus_num = bus_num;
   devcfg.pDevName = NULL;

   status = HBI_open(&handle,&devcfg);
   if(status != HBI_STATUS_SUCCESS)
   {
       printf("dev open error\n");
       HBI_term();
       return -1;
   }

   if(strcmp(argv[i],"-get")==0)
   {
     i++;
     if(strcmp(argv[i],"rout")==0)
     {
         status = zl380xx_get_rout_gain(handle, &gain);
         if(status == HBI_STATUS_SUCCESS)
         {
            printf("RD: ROUT gain = %d dB\n",gain);
         }
         else
         {
            printf("HBI_read failed. Error 0x%x\n",status);
            HBI_close(handle);
            HBI_term();
         }
     }
     else if(strcmp(argv[i],"sout")==0)
     {
         status = zl380xx_get_sout_gain(handle, &gain);
         if(status == HBI_STATUS_SUCCESS)
         {
            printf("RD: SOUT gain = %d dB\n",gain);
         }
         else
         {
            printf("HBI_read failed. Error 0x%x\n",status);
            HBI_close(handle);
            HBI_term();
         }
     } else
     {
         printf("invalid option %s \n", argv[i]);
         HBI_close(handle);
         HBI_term();
     }
   }
   if((i<argc) && strcmp(argv[i],"-set")==0)
   {
     int optpos = ++i;

     if(argc < 4)
     {
         printf("missing argument\n");
         usage();
         HBI_close(handle);
         HBI_term();
         return -1;
     }
     

     gain = strtol(argv[++i],NULL,0);

     if(strcmp(argv[optpos],"sout")==0)
     {
         status = zl380xx_set_sout_gain(handle,gain);
         if(status == HBI_STATUS_SUCCESS)
         {
            printf("WR: SOUT gain = %d dB\n",gain);
         }
         else
         {
            printf("zl380xx_set_sout_gain failed. Error 0x%x\n",status);
            HBI_close(handle);
            HBI_term();
         }
     }
     else if(strcmp(argv[optpos],"rout")==0)
     {
         status = zl380xx_set_rout_gain(handle,gain);
         if(status == HBI_STATUS_SUCCESS)
         {
            printf("WR: ROUT gain = %d dB\n",gain);
         }
         else
         {
            printf("zl380xx_set_rout_gain failed. Error 0x%x\n",status);
            HBI_close(handle);
            HBI_term();
         }
     } 
     else
     {
         printf("invalid option %s \n", argv[i]);
         HBI_close(handle);
         HBI_term();
     }

   }

   status = HBI_close(handle);
   if(status != HBI_STATUS_SUCCESS)
   {
      printf("dev close error\n");
      return -1;
   }

   status = HBI_term();
   if(status != HBI_STATUS_SUCCESS)
   {
      printf("driver term error\n");
      return -1;
   }

   return 0;

}

